package org.cweili.rayminder.domain

import groovy.transform.AutoClone
import groovy.transform.Canonical
import groovy.transform.ToString
import org.apache.commons.lang3.StringUtils

/**
 *
 * @author Cweili
 * @version 13-9-4 下午9:26
 *
 */
@Canonical
@ToString(includeNames = true)
@AutoClone
class Sort implements Iterable<Order> {

	public static final Direction DEFAULT_DIRECTION = Direction.ASC;

	List<Order> orders;

	/**
	 * Creates a new {@link Sort} instance using the given {@link Order}s.
	 *
	 * @param orders must not be {@literal null}.
	 */
	Sort(Order... orders) {
		this(Arrays.asList(orders));
	}

	/**
	 * Creates a new {@link Sort} instance.
	 *
	 * @param orders must not be {@literal null} or contain {@literal null}.
	 */
	Sort(List<Order> orders) {

		if (!orders || orders.isEmpty()) {
			throw new IllegalArgumentException("You have to provide at least one sort property to sort by!");
		}

		this.orders = orders;
	}

	/**
	 * Creates a new {@link Sort} instance. Order defaults to {@value Direction#ASC}.
	 *
	 * @param properties must not be {@literal null} or contain {@literal null} or empty strings
	 */
	Sort(String... properties) {
		this(DEFAULT_DIRECTION, properties);
	}

	/**
	 * Creates a new {@link Sort} instance.
	 *
	 * @param direction defaults to {@linke Sort # DEFAULT_DIRECTION} (for {@literal null} cases, too)
	 * @param properties must not be {@literal null} or contain {@literal null} or empty strings
	 */
	Sort(Direction direction, String... properties) {
		this(direction, properties == null ? new ArrayList<String>() : Arrays.asList(properties));
	}

	/**
	 * Creates a new {@link Sort} instance.
	 *
	 * @param direction
	 * @param properties
	 */
	Sort(Direction direction, List<String> properties) {

		if (properties == null || properties.isEmpty()) {
			throw new IllegalArgumentException("You have to provide at least one property to sort by!");
		}

		this.orders = new ArrayList<Order>(properties.size());

		for (String property : properties) {
			this.orders.add(new Order(direction, property));
		}
	}

	/**
	 * Returns a new {@link Sort} consisting of the {@link Order}s of the current {@link Sort} combined with the given
	 * ones.
	 *
	 * @param sort can be {@literal null}.
	 * @return
	 */
	Sort and(Sort sort) {

		if (sort == null) {
			return this;
		}

		ArrayList<Order> these = new ArrayList<Order>(this.orders);

		for (Order order : sort) {
			these.add(order);
		}

		return new Sort(these);
	}

	/**
	 * Returns the order registered for the given property.
	 *
	 * @param property
	 * @return
	 */
	Order getOrderFor(String property) {

		for (Order order : this) {
			if (order.getProperty().equals(property)) {
				return order;
			}
		}

		return null;
	}

	/*
	 * (non-Javadoc)
	 * @see java.lang.Iterable#iterator()
	 */

	Iterator<Order> iterator() {
		return this.orders.iterator();
	}

	/**
	 * Enumeration for sort directions.
	 *
	 * @author Oliver Gierke
	 */
	static enum Direction {

		ASC, DESC;

		/**
		 * Returns the {@link Direction} enum for the given {@link String} value.
		 *
		 * @param value
		 * @return
		 */
		static Direction fromString(String value) {

			try {
				return Direction.valueOf(value.toUpperCase(Locale.US));
			} catch (Exception e) {
				throw new IllegalArgumentException(String.format(
						"Invalid value '%s' for orders given! Has to be either 'desc' or 'asc' (case insensitive).", value), e);
			}
		}
	}

	/**
	 * PropertyPath implements the pairing of an {@link Direction} and a property. It is used to provide input for
	 * {@link Sort}
	 *
	 * @author Oliver Gierke
	 */
	@Canonical
	@ToString(includeNames = true)
	@AutoClone
	static class Order {

		Direction direction;
		String property;

		/**
		 * Creates a new {@link Order} instance. if order is {@literal null} then order defaults to
		 * {@link Sort#DEFAULT_DIRECTION}
		 *
		 * @param direction can be {@literal null}, will default to {@link Sort#DEFAULT_DIRECTION}
		 * @param property must not be {@literal null} or empty.
		 */
		Order(Direction direction, String property) {

			if (StringUtils.isEmpty(property)) {
				throw new IllegalArgumentException("Property must not null or empty!");
			}

			this.direction = direction == null ? DEFAULT_DIRECTION : direction;
			this.property = property;
		}

		/**
		 * Creates a new {@link Order} instance. Takes a single property. Direction defaults to
		 * {@link Sort#DEFAULT_DIRECTION}.
		 *
		 * @param property must not be {@literal null} or empty.
		 */
		Order(String property) {
			this(DEFAULT_DIRECTION, property);
		}

		/**
		 * Returns whether sorting for this property shall be ascending.
		 *
		 * @return
		 */
		boolean isAscending() {
			return this.direction.equals(Direction.ASC);
		}

		/**
		 * Returns a new {@link Order} with the given {@link Order}.
		 *
		 * @param order
		 * @return
		 */
		Order with(Direction order) {
			return new Order(order, this.property);
		}

		/**
		 * Returns a new {@link Sort} instance for the given properties.
		 *
		 * @param properties
		 * @return
		 */
		Sort withProperties(String... properties) {
			return new Sort(this.direction, properties);
		}
	}
}